/**
 * @fileOverview DOM Query Class
 * @author Xaephis <xaephis@gmail.com>
 * @version v 4.05 Sun May 29 2011 12:47:39 GMT+0800 (China Standard Time)
 */
Jx.declare('Jx.dom.Query', {

	$$singleton: true,

	$$extend: 'Jx.xml.SelectorQuery',

	$$callback: function(Constructor, Base, Instance){

		var Browser = Instance.Browser,

			overrides = {},

			collectionToArray = Browser.supports('domCollectionToArray'),

			byClassName = Browser.supports('domGetElementsByClassName');


		overrides.supportsByClassName = byClassName;

		if (collectionToArray && byClassName){

			overrides.createCollection = function(context, fn){

				var hashFind = fn.hashFindMode,

					hashAnchor = fn.hashAnchor,

					byClassName = fn.typeSelect == '*' && fn.getClassNames,

					queryAllSelector = fn.useCollectionQueryAll,

					Base = this.$base().createCollection,

					node;

				if (queryAllSelector){

					return [

						Array.prototype.slice.call(context.querySelectorAll(queryAllSelector), 0)

					];

				} else if (hashFind){

					node = (context.nodeType == 9 ? context : context.ownerDocument).getElementById(hashFind);

					return [node ? [node] : []];

				} else if (hashAnchor){

					node = (context.nodeType == 9 ? context : context.ownerDocument).getElementById(hashAnchor);

					return node ?

						Base.call(this, node, fn)

						:

						[[]];

				} else if (byClassName){

					return [

						Array.prototype.slice.call(context.getElementsByClassName(byClassName), 0)

					];

				} else {

					return Base.call(this, context, fn);

				}

			};

		} else if (byClassName){

			overrides.createCollection = function(context, fn){

				var hashFind = fn.hashFindMode,

					hashAnchor = fn.hashAnchor,

					byClassName = fn.typeSelect == '*' && fn.getClassNames,

					queryAllSelector = fn.useCollectionQueryAll,

					Base = this.$base().createCollection,

					node;

				if (queryAllSelector){

					return [

						context.querySelectorAll(queryAllSelector)

					];

				} else if (hashFind){

					node = (context.nodeType == 9 ? context : context.ownerDocument).getElementById(hashFind);

					return [node ? [node] : []];

				} else if (hashAnchor){

					node = (context.nodeType == 9 ? context : context.ownerDocument).getElementById(hashAnchor);

					return node ?

						Base.call(this, node, fn)

						:

						[[]];

				} else if (byClassName){

					return [

						context.getElementsByClassName(byClassName)

					];

				} else {

					return Base.call(this, context, fn);

				}

			};

		//-- only collection to array
		} else if (collectionToArray){

			overrides.createCollection = function(context, fn){

				var hashFind = fn.hashFindMode,

					hashAnchor = fn.hashAnchor,

					queryAllSelector = fn.useCollectionQueryAll,

					Base = this.$base().createCollection,

					node;

				if (queryAllSelector){

					return [

						Array.prototype.slice.call(context.querySelectorAll(queryAllSelector), 0)

					];

				} else if (hashFind){

					node = (context.nodeType == 9 ? context : context.ownerDocument).getElementById(hashFind);

					return [node ? [node] : []];

				} else if (hashAnchor){

					node = (context.nodeType == 9 ? context : context.ownerDocument).getElementById(hashAnchor);

					return node ?

						Base.call(this, node, fn)

						:

						[[]];


				} else {

					return Base.call(this, context, fn);

				}

			};

		}

		//-- assign jx
		Jx.assign(Jx, {

			select: function(){

				return Instance.select.apply(Instance, arguments);

			},

			is: function(){

				return Instance.is.apply(Instance, arguments);

			}

		});



		//-- remove support for pseudos :nth-... it's sooo fuck'n slow
		var supportDb = Instance.queryAllSupportedFeature,

			re;

		if (Jx.browserIs('ie >= 8') && supportDb){

			re = /^pseudo\[\:nth/;

			for(var n in supportDb){

				if (re.test(n)){

					supportDb[n] = false;

				}

			}

		}

		return overrides;

	},

	useNative: true,

	queryAllFilters: undefined,

	queryAllSupportedFeature: false,

	queryAllSupportDetection: {

		'combinator[ ]': function(testElement){

			return testElement.querySelectorAll('div strong').length >= 1;

		},

		'combinator[>]': function(testElement){

			return testElement.querySelectorAll('div > strong').length == 1;

		},

		'combinator[~]': function(testElement){

			return testElement.querySelectorAll('div ~ b').length == 1;

		},

		'combinator[+]': function(testElement){

			return testElement.querySelectorAll('div + div').length == 1;

		},

		'attrMatch[=]': function(testElement){

			return testElement.querySelectorAll('span[lang=en-us]').length == 1;

		},

		'attrMatch[|=]': function(testElement){

			return testElement.querySelectorAll('span[lang|=en]').length == 1;

		},

		'attrMatch[~=]': function(testElement){

			return testElement.querySelectorAll('span[class~=buang]').length == 1;

		},

		'attrMatch[^=]': function(testElement){

			return testElement.querySelectorAll('span[class^=buang]').length == 1;

		},

		'attrMatch[$=]': function(testElement){

			return testElement.querySelectorAll('span[class$=mo]').length == 1;

		},

		'pseudo[:first-child]': function(testElement){

			return testElement.querySelectorAll(':first-child').length >= 1;

		},

		'pseudo[:last-child]': function(testElement){

			return testElement.querySelectorAll(':last-child').length >= 1;

		},

		'pseudo[:only-child]': function(testElement){

			return testElement.firstChild.querySelectorAll(':only-child').length == 1;

		},

		'pseudo[:nth-child]': function(testElement){

			return testElement.querySelectorAll('div:nth-child(odd)').length == 1;

		},

		'pseudo[:nth-last-child]': function(testElement){

			return testElement.querySelectorAll('div:nth-last-child(odd)').length == 1;

		},

		'pseudo[:first-of-type]': function(testElement){

			return testElement.querySelectorAll('div:first-of-type').length == 1;

		},

		'pseudo[:last-of-type]': function(testElement){

			return testElement.querySelectorAll('div:last-of-type').length == 1;

		},

		'pseudo[:only-of-type]': function(testElement){

			return testElement.querySelectorAll('span:only-of-type').length == 1;

		},

		'pseudo[:nth-of-type]': function(testElement){

			return testElement.querySelectorAll('div:nth-of-type(odd)').length >= 1;

		},

		'pseudo[:nth-last-of-type]': function(testElement){

			return testElement.querySelectorAll('div:nth-last-of-type(odd)').length == 1;

		},

		'pseudo[:empty]': function(testElement){

			return testElement.querySelectorAll('b:empty').length == 1;

		},

		'pseudo[:not]': function(testElement){

			return testElement.querySelectorAll(':not(h1)').length >= 1;

		},

		'pseudo[:lang]': function(testElement){

			return testElement.querySelectorAll('span:lang(en)').length == 1;

		},

		'pseudo[:root]': function(testElement){

			return document.querySelectorAll('html:root').length == 1;

		},

		'pseudo[:enabled]': function(testElement){

			return document.querySelectorAll('input[type=button]:enabled').length == 0;

		},

		'pseudo[:disabled]': function(testElement){

			return document.querySelectorAll('input[type=button]:disabled').length == 0;

		},

		'pseudo[:checked]': function(testElement){

			return document.querySelectorAll('input[type=checkbox]:checked').length == 0;

		}

	},

	constructor: function(){

		var Browser = this.Browser,

			overrides = {},

			queryAllTesters = this.queryAllSupportDetection,

			supported = this.queryAllSupportedFeature,

			result, fn, testElement;

		this.queryAllFilters = {};

		this.jxAllFilters = {};

		//-- detect query tests
		if (!supported && Browser.supports('domQuerySelectorAll')){

			supported = this.queryAllSupportedFeature = {};

			testElement = document.createElement('div');

			testElement.innerHTML = '<span lang="en-us" class="buang mo"><strong>test</strong></span>'

				+ '<div>buang</div>'

				+ '<div><strong>buang</strong></div>'

				+ '<input type="button" /><input type="button" disabled="disabled" />'

				+ '<input type="checkbox" checked="checked" /><input type="button" class="en" disabled="disabled" />'

				+ '<b></b>';

			for(var name in queryAllTesters){

				fn = queryAllTesters[name];

				try {

					result = fn.call(this, testElement) !== false;

				} catch (e){

					result = false;

				}

				supported[name] = !!result;

			}



		}

		this.$baseCall();


	},

	'compile-cache-gen-nodeId-property': '_nodeCacheId',

	'compile-cache-elementIndex-property': '_elementIndex',

	'compile-matchTagName': function(nodeVarName, name, ns){

		var Str = this.String,

			tag = ns != '*' && ns ? Str.escape(ns) : '';

		if (name){

			tag = (tag ? tag+':' : '') + Str.escape(name);

			return nodeVarName+'.tagName == "'+tag.toUpperCase()+'"';

		} else if (tag){

			return nodeVarName+'.tagName.substring(0, '+(tag.length+1)+') == "'+tag.toUpperCase()+':"';

		} else {

			return 'true';

		}

	},

	'compile-matchTagNameVar': function(nodeVarName, varName, ns){

		var Str = this.String,

			tag = ns != '*' && ns ? Str.escape(ns) : '';

		if (ns){

			return nodeVarName+'.tagName.substring(0, '+varName+'.length + 1) == ('+varName+'.toUpperCase()+":")';

		} else {

			return nodeVarName+'.tagName == '+varName+'.toUpperCase()';

		}

	},

	'compile-getAttribute': function(varName, name){

		if (name == 'for'){

			return varName+'=node.htmlFor';

		} else if (name == 'class' || name == 'className'){

			return varName+'=node.className';

		} else if (name == 'style'){

			return varName+'=node.style.cssText';

		} else {

			return varName+'=node.getAttribute("'+this.String.escape(name)+'")';

		}

	},

	//-- only applicable to html
	'compile-getNodeProperty': function(nodeVarName, varName, propertyName){

		return varName+'='+nodeVarName+'.'+propertyName+';';

	},

	'compile-setNodeProperty': function(nodeVarName, varName, propertyName, value){

		return varName ?

			varName+' = '+nodeVarName+'.'+propertyName+' = '+value+';'

			:

			nodeVarName+'.'+propertyName+' = '+value+';';

	},

	//-- HTML UI pseudo selector (requires these elements to be a descendant of <body>)
	'compile-pseudo[:enabled]': function(data, lexeme, marker, name, value){

		var varName = 'attrValue';

		lexeme.vars = [varName];

		lexeme.ltype = 'type_attribute';

		lexeme.code = '("disabled" in node) && !node.disabled';

	},

	'compile-pseudo[:disabled]': function(data, lexeme, marker, name, value){

		lexeme.ltype = 'type_attribute';

		lexeme.code = '("disabled" in node) && node.disabled';

	},

	'compile-pseudo[:checked]': function(data, lexeme, marker, name, value){

		lexeme.ltype = 'type_attribute';

		lexeme.code = '("checked" in node) && node.checked';

	},



	//-- optimize hash
	'compile-rule-Hash': function(lexeme, values){

		this.compileData.currentHash = values[1].value;

		return this.$baseCall(arguments);

	},

	'compile-rule-Class': function(lexeme, values){

		this.compileData.classNames.push(values[1].value);

		return this.$baseCall(arguments);

	},

	'compile-rule-Attribute': function(lexeme, values){

		var data = this.compileData,

			op, name, value;

		//-- is a hash
		if (values.length > 3){

			name = values[1].value;

			op = values[2].value;

			value = values[3].value;

			if (name == 'id' && op == '='){

				data.currentHash = value;

			} else if ((name == 'class' || name == 'className') &&

				(op == '~=' || name == '=') &&

				value

			){

				data.classNames.push(value);

			}

		}

		return this.$baseCall(arguments);

	},

	'compile-rule-Simple_Selector': function(lexeme, values){

		//-- create filter
		var data = this.compileData,

			hash = data.currentHash,

			selector = lexeme.getCode(),

			ret = this.$baseCall(arguments),

			compiledSelectorName,

			compiledValidateSelectorName, set, soSimple, fn;

		//-- hash
		if (hash){

			set = data.sets[data.sets.length-1];

			set.hash = hash;

			if (!((compiledSelectorName = this.getCompileIndexName(selector, 'filter', true)) in this)){

				soSimple = !set.attrMatch.length && !set.pseudoMatch && set.tag == '*' && set.ns == '*';

				compiledValidateSelectorName = this.getCompileIndexName(selector, 'validator', true);

				fn = this[compiledSelectorName] = new Function('collections,context,limit,cache', [

					'var root = context.nodeType == 9 ? cache.docRoot : context,',

						'collection = collections[0],',

						'ret, target;',

					'if (collection.length && (target = collection[0])){',

						//-- check context
						'for(var pn=target.parentNode; pn; pn = pn.parentNode){',

							'if (pn === root){',

								soSimple ?

									'return [target].slice(0, limit);'

									:

									'if (this.'+compiledValidateSelectorName+'(target,context,cache)){ return [target].slice(0, limit);}',


								'break;',

							'}',

						'}',

					'}',

					'return [];'

				].join('\n'));

				fn.typeSelect = data.types.length ? data.types.slice(0) : ['*'];

				fn.pseudoTypeIndexes = data.indexedPseudoTypes.slice(0);

				fn.hashFindMode = hash;

				//-- queue selector to compiler
				if (!(compiledValidateSelectorName in this)){

					data.toCompile.push(selector);

				}

				this.onCompile(false, compiledSelectorName, selector, lexeme);

			}

		}

		return ret;

	},

	'compile-rule-Selector': function(lexeme, values){

		var ret = this.$baseCall(arguments),

			selector = lexeme.getCode(),

			data = this.compileData,

			set = data.sets[data.sets.length-1],

			hash = set.hash,

			compiledValidateSelectorName = this.getCompileIndexName(selector, 'validator', true),

			compiledSelectorName, fn, soSimple;



		//-- create filter
		if (hash &&

			!((compiledSelectorName = this.getCompileIndexName(selector, 'filter', true)) in this)

		){

			soSimple = !set.attrMatch.length && !set.pseudoMatch && set.tag == '*' && set.ns == '*';

			compiledValidateSelectorName = this.getCompileIndexName(selector, 'validator', true);

			fn = this[compiledSelectorName] = new Function('collections,context,limit,cache', [

				'var root = context.nodeType == 9 ? cache.docRoot : context,',

					'collection = collections[0],',

					'target;',

				'if (collection.length && (target = collection[0])){',

					//-- check context
					'for(var pn=target.parentNode; pn; pn = pn.parentNode){',

						'if (pn === root){',

							soSimple ?

								'return [target].slice(0, limit);'

								:

								'if (this.'+compiledValidateSelectorName+'(target,context,cache)){ return [target].slice(0, limit);}',


							'break;',

						'}',

					'}',

				'}',

				'return [];'

			].join('\n'));

			fn.typeSelect = data.types.length ? data.types.slice(0) : ['*'];

			fn.pseudoTypeIndexes = data.indexedPseudoTypes.slice(0);

			fn.hashFindMode = hash;

			this.onCompile(false, compiledSelectorName, selector, lexeme);

		}

		//-- clear hash
		data.currentHash = '';

		return ret;

	},

	'compile-rule-Query': function(lexeme, values){

		//-- find hash in group
		var ret = this.$baseCall(arguments),

			data = this.compileData,

			classNames = data.classNames,

			groups = data.groups,

			types = data.types,

			fn = this[data.filterFnName], group, sets, set, hashed;

		fn.getClassNames = '';

		//-- one on one
		if (types.length == 1 && groups.length == 1){

			sets = groups[0].sets;

			if ((typeof fn.hashFindMode == 'undefined')){

				//-- find hash
				for(var l = sets.length; l--;){

					set = sets[l];

					if (set.hash){

						hashed = set;

						break;

					}

				}


				//-- anchor hash
				if (hashed){

					fn.hashAnchor = hashed.hash;

				}

			}

			//-- by className
			if (classNames.length){

				fn.getClassNames = classNames.join(' ');

			}



		}

		return ret;

	},

	onStartCompile: function(){

		//-- support for "getElementsByClassName"
		this.compileData.classNames = [];

		this.$baseCall(arguments);

	},

	onCompile: function(isValidator, name, selector, lexeme){

		var production = lexeme.production,

			nativeFn, jxFn;


		if (!isValidator){

			jxFn = this.jxAllFilters[name] = this[name];

			//-- check if it supports queryAll
			if (this.queryAllSupportsCurrentCompiledData()){

				nativeFn = this.queryAllFilters[name] = new Function('collections,context,limit,cache', [

					this.Browser.supports('domCollectionToArray') ?

						[

							'var collection = collections[0],',

								'result;',

							'if (limit == collection.length){',

								'return collection;',

							'} else {',

								'result = [];',

								'for(var ri = limit; ri--;){',

									'result[ri] = collection[ri];',

								'}',

								'return result;',

							'}'

						].join('\n')

						:

						[

							'var collection = collections[0],',

								'result = [];',

							'for(var ri = limit; ri--;){',

								'result[ri] = collection[ri];',

							'}',

							'return result;'

						].join('\n')

				].join('\n'));

				//-- querySelectorAll must create the collection
				nativeFn.useCollectionQueryAll = selector;

			}




		}

		this.$baseCall(arguments);

	},


	//useNative

	onBeforeSelect: function(fnName, fn){

		var nfilters = this.queryAllFilters;

		//console.log('use native? ', this.useNative && (fnName in nfilters), ' fn: ', nfilters[fnName].toString());

		this[fnName] = this.useNative && (fnName in nfilters) ?

			nfilters[fnName]

			:

			this.jxAllFilters[fnName];

	},

	getNodeName: function(dom){

		return dom.tagName;

	},

	getNodeNameMatch: function(name){

		return name.toUpperCase();

	},

	queryAllSupportsCurrentCompiledData: function(){

		var features = this.queryAllSupportedFeature,

			parts = this.compileData.parts,

			pseudos = parts.pseudos,

			attribOps = parts.attributeOperators,

			combinators = parts.combinators,

			doc = document, l, name, part;

		if (features){

			//-- check combinators
			for(l = combinators.length; l--;){

				name = 'combinator['+(part = combinators[l])+']';

				//-- try it!
				if (name in features){

					if (!features[name]){

						return false;

					}

				} else {

					try {

						doc.querySelectorAll('div '+part+' span');

						features[name] = true;

					} catch(e){

						return features[name] = false;

					}

				}

			}

			//-- check attribute operators
			for(l = attribOps.length; l--;){

				name = 'attribMatch['+(part = attribOps[l])+']';

				//-- try it!
				if (name in features){

					if (!features[name]){

						return false;

					}

				} else {

					try {

						doc.querySelectorAll('div[id'+part+'buang]');

						features[name] = true;

					} catch(e){

						return features[name] = false;

					}

				}

			}

			//-- check pseudos
			for(l = pseudos.length; l--;){

				name = 'pseudo['+(part = pseudos[l])+']';

				//-- try it!
				if (name in features){

					if (!features[name]){

						return false;

					}

				} else {

					try {

						doc.querySelectorAll('div'+part);

						features[name] = true;

					} catch(e){

						return features[name] = false;

					}

				}

			}


			return true;

		}

		return false;

	},

	createCollection: function(context, fn){

		var hashFind = fn.hashFindMode,

			hashAnchor = fn.hashAnchor,

			queryAllSelector = fn.useCollectionQueryAll,

			Base = this.$base().createCollection,

			node;

		if (queryAllSelector){

			return [context.querySelectorAll(queryAllSelector)];

		} else if (hashFind){

			node = (context.nodeType == 9 ? context : context.ownerDocument).getElementById(hashFind);

			return [node ? [node] : []];

		} else if (hashAnchor){

			node = (context.nodeType == 9 ? context : context.ownerDocument).getElementById(hashAnchor);

			return node ?

				Base.call(this, node, fn)

				:

				[[]];


		} else {

			return Base.call(this, context, fn);

		}

	},



	nativeSelectSupports: function(name){

		var features = this.queryAllSupportedFeature,

			ret = false;

		return features && Jx.isString(name) && (name in features) ?

			features[name]

			:

			false;

	}



});
